iT邦幫忙

2022 iThome 鐵人賽

DAY 26
0
Modern Web

擁抱 .Net Core系列 第 26

[Day26] 驗證 Authentication - 3

  • 分享至 

  • xImage
  •  

昨天頭痛沒寫完(之前的待補有的已經補上了)
接續昨天提到的AuthenticationMiddleware

AuthenticationMiddleware

AuthenticationMiddleware.cs

public IAuthenticationSchemeProvider Schemes { get; set; }

public async Task Invoke(HttpContext context)
{
    context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
    {
        OriginalPath = context.Request.Path,
        OriginalPathBase = context.Request.PathBase
    });

    var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
    foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
    {
        var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;
        if (handler != null && await handler.HandleRequestAsync())
        {
            return;
        }
    }

    var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
    if (defaultAuthenticate != null)
    {
        var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
        if (result?.Principal != null)
        {
            context.User = result.Principal;
        }
        if (result?.Succeeded ?? false)
        {
            var authFeatures = new AuthenticationFeatures(result);
            context.Features.Set<IHttpAuthenticationFeature>(authFeatures);
            context.Features.Set<IAuthenticateResultFeature>(authFeatures);
        }
    }

    await _next(context);
}

IAuthenticationSchemeProvider

IAuthenticationSchemeProvider 負責提供 AuthenticationScheme 物件
AuthenticationScheme 物件除了負責記錄驗證的類型外
也負責記錄所使用的IAuthenticationHandler
AuthenticationScheme.cs

public class AuthenticationScheme
{
    public string Name { get; }
    public string? DisplayName { get; }
    public Type HandlerType { get; }
}

HandlerType 物件即為 對應的IAuthenticationHandler

public interface IAuthenticationSchemeProvider
{
    Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync();

    Task<AuthenticationScheme?> GetSchemeAsync(string name);

    Task<AuthenticationScheme?> GetDefaultAuthenticateSchemeAsync();

    Task<AuthenticationScheme?> GetDefaultChallengeSchemeAsync();

    Task<AuthenticationScheme?> GetDefaultForbidSchemeAsync();

    Task<AuthenticationScheme?> GetDefaultSignInSchemeAsync();

    Task<AuthenticationScheme?> GetDefaultSignOutSchemeAsync();

    void AddScheme(AuthenticationScheme scheme);

    bool TryAddScheme(AuthenticationScheme scheme)

    void RemoveScheme(string name);

    Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync();
}

除了提供取驗證用的 AuthenticationScheme
也提供其他IAuthenticationHandler 會用到的方法的預設驗證方法

使用jwt驗證

這邊使用微軟的nuget套件
Microsoft.AspNetCore.Authentication.JwtBearer

首先我們要定義一個能取得token 的function

TokenService.cs

public class TokenService
{
    private readonly JwtSecurityTokenHandler _jwtSecurityTokenHandler;
    private readonly IConfiguration _configuration;

    public TokenService(JwtSecurityTokenHandler jwtSecurityTokenHandler, IConfiguration configuration)
    {
        _jwtSecurityTokenHandler = jwtSecurityTokenHandler;
        _configuration = configuration;
    }

    public string CreateJwtToken(Member member)
    {
        /// 設定使用者身分
        var identity = new ClaimsIdentity(new[]
        {
            new Claim(JwtRegisteredClaimNames.Name, member.Account),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.Email, member.Email),   
        });

        var issuer = "issuer";//_configuration["jwt:Issuer"];
        var key = "kewernmkjohiusdfy";//_configuration["jwt:key"];

        /// 加密
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
        var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);

        /// 設定token
        var securityTokenDescriptor = new SecurityTokenDescriptor
        {
            Issuer = issuer,
            Subject = identity,
            Expires = DateTime.Now.AddDays(7),
            SigningCredentials = signingCredentials
        };
        
        /// 產生jwt Token
        var securityToken = _jwtSecurityTokenHandler.CreateToken(securityTokenDescriptor);
        var token = _jwtSecurityTokenHandler.WriteToken(securityToken);

        return token;
    }

    public class Member
    {
        public string Account { get; set; }
        public string Password { get; set; }
        public string Email { get; set; }
    }
}

做一個對應的Controller來讓使用者可以取得Token

[ApiController]
[Route("api/[controller]")]
public class TokenController
{
    private readonly TokenService _tokenService;

    private IReadOnlyList<Member> _fakeMembers = new List<Member>()
    {
        new() { Account = "Admin", Password = "Admin", Email = "admin@gmail.com" },
        new() { Account = "User", Password = "User", Email = "user@gmail.com" } 
    };
    
    public TokenController(TokenService tokenService)
    {
        _tokenService = tokenService;
    }
    
   [HttpPost]
    public string CreateToken(LoginRequest loginRequest)
    {
        var member = _fakeMembers.FirstOrDefault(x => x.Account == loginRequest.Account && x.Password == loginRequest.Password);
        if(member is null) throw new Exception("帳號或密碼錯誤");
        return _tokenService.CreateJwtToken(member);
    }

    public record LoginRequest(string Account, string Password);
}

這個TokenController 仿造了登入取得token

最後需要program中加上jwt的驗證設定跟驗證middleware

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton(new JwtSecurityTokenHandler());
builder.Services.AddSingleton<TokenService>();
builder.Services.AddHealthChecks();

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
    options.IncludeErrorDetails = true;
    var key = "kewernmkjohiusdfy";//builder.Configuration.GetValue<string>("JwtSettings:SignKey");
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidIssuer = "issuer",//builder.Configuration.GetValue<string>("JwtSettings:Issuer"),
        ValidateLifetime = true,
        ValidateAudience = false,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
    };
});

var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/health").RequireAuthorization();

app.MapControllers();
await app.RunAsync();

實際上前幾篇提的介面的實作都在 AddJwtBearer 中,
Jwt的Default Scheme 是 Bearer
所以只要打api的時候request header 帶上 Bearer $jwttoken 就可以使用了

我們在healthcheck中加入驗證
可以透過打healthcheck確認
/health

https://ithelp.ithome.com.tw/upload/images/20221008/20109549b6uCY5ktCa.png


上一篇
[Day25] 驗證 - Authentication - 2
下一篇
[Day27] 授權 - Authorized - 1
系列文
擁抱 .Net Core30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言